import jwt from 'jsonwebtoken';
import redisClient from '../utils/redis.js';
import { publicKey, privateKey } from '../utils/key.js';

// Token 自动续期阈值（剩余15分钟时自动续期）
const RENEW_THRESHOLD = 15 * 60; // 单位：秒
// 错误统一处理
const handleAuthError = (ctx, err) => {
  let message = '身份验证失败';
  let code = 'AUTH_FAILED';

  switch (err.name) {
    case 'TokenExpiredError':
      code = 'TOKEN_EXPIRED';
      message = '身份凭证已过期';
      break;
    case 'JsonWebTokenError':
      code = 'INVALID_TOKEN';
      message = '无效身份凭证';
      break;
  }

  ctx.status = 401;
  ctx.body = { code, message };
};
// 核心中间件：处理 Token 验证、续期和失效检查
export const auth = async (ctx, next) => {
  console.log('%c [ next ]-28', 'font-size:13px; background:pink; color:#bf2c9f;', next)
  const authHeader = ctx.headers.authorization;

  // 1. 检查是否存在 Token
  if (!authHeader?.startsWith('Bearer ')) {
    ctx.throw(401, JSON.stringify({
      code: 'MISSING_TOKEN',
      message: '未提供身份凭证'
    }));
  }

  const token = authHeader.split(' ');

  try {
    // 2. 检查 Token 是否在黑名单
    const isRevoked = await redisClient.exists(`blacklist:${token}`);
    if (isRevoked) ctx.throw(401, '凭证已失效');

    // 3. 验证 Token 有效性
    const decoded = jwt.verify(token[1], publicKey, { algorithms: ['RS256'] });

    // 4. 自动续期逻辑
    const now = Math.floor(Date.now() / 1000);
    const remainingTTL = decoded.exp - now;

    if (remainingTTL < RENEW_THRESHOLD) {
      const newToken = generateToken(decoded.userId, decoded.username);
      await addToBlacklist(token, remainingTTL); // 将旧 Token 加入黑名单
      ctx.set('X-Renewed-Token', newToken); // 通过响应头发送新 Token
    }

    // 5. 附加用户信息到上下文
    ctx.state.user = decoded;
    await next();
  } catch (err) {
    handleAuthError(ctx, err);
  }
};

// 生成新 Token
const generateToken = (userId, username) => {
  return jwt.sign(
    { userId, username },
    privateKey,
    { algorithm: 'RS256', expiresIn: '1h' }
  );
};

// 将 Token 加入黑名单
const addToBlacklist = async (token, ttl) => {
  if (ttl > 0) await redisClient.set(`blacklist:${token}`, 'revoked', 'EX', ttl);
};

